#include <ctype.h>
#include <assert.h>

#include <vector>

#include "AstNode.h"
#include "ConstantPool.h"
#include "MethodInfo.h"
#include "ClassFile.h"

AstNodeLiteral::AstNodeLiteral(ConstantPool * cp, const char * text)
{
  string_index=cp->addString(text);
}

void AstNodeLiteral::dump()
{
  printf("literal<%d>",string_index);
}

int AstNodePrintLiteral::compile(ClassFile * klass, MethodInfo * method)
{
  u2 constantFieldRef=klass->addFieldRef(
      "java/lang/System","out", "Ljava/io/PrintStream;");

  u2 constantMethodRef;

  if(endLine)
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","println", "(Ljava/lang/String;)V");
  else
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","print", "(Ljava/lang/String;)V");

  method->assemble(OPC_getstatic,constantFieldRef);
  literal->compile(klass,method);
  method->assemble(OPC_invokevirtual,constantMethodRef);

  return 0;
}

int AstNodeLiteral::compile(ClassFile * klass, MethodInfo * method)
{
  method->assemble(OPC_ldc1,string_index);
  return 0;
}

AstNodePrintLiteral::AstNodePrintLiteral(AstNodeLiteral * son, bool endLine)
{
  this->literal=son;
  this->endLine=endLine;
}

void AstNodePrintLiteral::dump()
{
  printf("print ");
  literal->dump();
  printf("\n");
}


AstNodeStatementList::AstNodeStatementList()
{
}

int  AstNodeStatementList::compile(ClassFile * klass, MethodInfo * method)
{
  for(u2 i=0;i<nodes.size();i++) {
    nodes[i]->compile(klass,method);
  }
  return 0;
}

void AstNodeStatementList::addNode(AstNode*node)
{
  nodes.push_back(node);
}

void AstNodeStatementList::dump()
{
  printf("STATEMENT LIST\n");
  for(u2 i=0;i<nodes.size();i++) {
    printf("[%03d] ",i);
    nodes[i]->dump();
  }
  printf("\n");
}

void AstNodeDoubleConstant::dump()
{
  printf("double<%d>",index);
}

int AstNodeDoubleConstant::compile(ClassFile * klass, MethodInfo * method)
{
  method->assemble(OPC_ldc2w,index);

  // TODO!!!!!!!
  return 0;
}

AstNodeDoubleConstant::AstNodeDoubleConstant(ConstantPool* cp,double value)
{
  index=cp->addDouble(value);
}

AstNodeBinaryOperator::AstNodeBinaryOperator(int op, AstNodeExpression * left, AstNodeExpression * rite)
{
  this->op=op;
  this->left=left;
  this->rite=rite;
}

void AstNodeBinaryOperator::dump()
{
  printf(" << ");
  left->dump();
  if(isprint(op)) printf(" %c ",op);
  rite->dump();
  printf(" >> ");
}

int AstNodeBinaryOperator::compile(ClassFile * klass, MethodInfo * method)
{
  left->compile(klass,method);
  rite->compile(klass,method);

  switch(op) {
    case '+':
     method->assemble(OPC_dadd);
     break;
    case '-':
     method->assemble(OPC_dsub);
     break;
    case '*':
     method->assemble(OPC_dmul);
     break;
    case '/':
     method->assemble(OPC_ddiv);
     break;
    default:
     assert(0);
  }
  return 0;
}

AstNodeAssignment::AstNodeAssignment(
    AstNode * lvalue, AstNodeExpression * expr)
{
  this->lvalue=lvalue;
  this->expr=expr;
}

int  AstNodeAssignment::compile(ClassFile * klass, MethodInfo * method)
{
  this->expr->compile(klass,method);
  this->lvalue->compile(klass,method);
  return 0;
}

void AstNodeAssignment::dump()
{
  printf("assign: ");
  expr->dump();
  printf(" to ");
  lvalue->dump();
  printf("\n");
}

AstNodeLValue::AstNodeLValue(u2 index)
{
  this->index=index;
}

int AstNodeLValue::compile(ClassFile * klass, MethodInfo * method)
{
  u2 ii=index*2;
  switch(ii) {
    case 0:
      method->assemble(OPC_dstore_0);
      break;
    case 1:
      method->assemble(OPC_dstore_1);
      break;
    case 2:
      method->assemble(OPC_dstore_2);
      break;
    default:
      method->assemble(OPC_dstore,ii);
      break;
  }
  return 0;
}

void AstNodeLValue::dump()
{
  printf("lvalue: %d",index);
}

AstNodePrintExpression::AstNodePrintExpression(AstNodeExpression * expr, bool endLine)
{
  this->expr=expr;
  this->endLine=endLine;
}

int AstNodePrintExpression::compile(ClassFile * klass, MethodInfo * method)
{
  u2 constantFieldRef=klass->addFieldRef(
      "java/lang/System","out", "Ljava/io/PrintStream;");

  u2 constantMethodRef;

  if(endLine)
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","println", "(D)V");
  else
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","print", "(D)V");

  method->assemble(OPC_getstatic,constantFieldRef);
  expr->compile(klass,method);
  method->assemble(OPC_invokevirtual,constantMethodRef);

  return 0;
}

void AstNodePrintExpression::dump()
{
  printf("printExpression ");
  expr->dump();
  printf("\n");
}

AstNodeExpressionVariable::AstNodeExpressionVariable(u2 symbIndex)
{
  this->index=symbIndex;
}

int AstNodeExpressionVariable::compile(ClassFile * klass, MethodInfo * method)
{
  u2 ii=index*2;
  switch(ii) {
    case 0:
      method->assemble(OPC_dload_0);
      break;
    case 1:
      method->assemble(OPC_dload_1);
      break;
    case 2:
      method->assemble(OPC_dload_2);
      break;
    default:
      method->assemble(OPC_dload,ii);
      break;
  }
  return 0;
}

void AstNodeExpressionVariable::dump()
{
  printf("exprVar<%d>",index);
}

AstNodeCompareExpression::AstNodeCompareExpression(
    AstNodeExpression * left, AstNodeExpression  * rite, u1 cmpOperator)
{
  this->left=left;
  this->rite=rite;
  this->oper=cmpOperator;
}

void AstNodeCompareExpression::dump()
{
  printf("cmp ");
  left->dump();
  switch(oper) {
    case _equal:
      printf("==");
      break;
    case _less:
      printf("<");
      break;
    case _greater:
      printf(">");
      break;
    case _less_equal:
      printf("<=");
      break;
    case _greater_equal:
      printf(">=");
      break;
    case _not_equal:
      printf("<>");
      break;
    default:
      assert(0);
  }
  printf(" ");
  rite->dump();
}

u1 AstNodeCompareExpression::getOpcodeForFalse()
{

  switch(oper) {
    case _equal:
      return OPC_ifne;
    case _less:
      return OPC_ifge;
    case _greater:
      return OPC_ifle;
    case _less_equal:
      return OPC_ifgt;
    case _greater_equal:
      return OPC_iflt;
    case _not_equal:
      return OPC_ifeq;
    default:
      assert(0);
  }
}

int AstNodeCompareExpression::compile(ClassFile * klass, MethodInfo * method)
{
  switch(oper) {
    case _equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _less:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _greater:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _less_equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _greater_equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _not_equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    default:
      assert(0);
  }
  return 0;
}

AstNodeBooleanOperator::AstNodeBooleanOperator(
    AstNodeBooleanOperator * left, AstNodeBooleanOperator  * rite, u1 oper)
{
  assert(oper!=_not || rite==0);
  this->left=left;
  this->rite=rite;
  this->oper=oper;
  this->cmp=0;
}

AstNodeBooleanOperator::AstNodeBooleanOperator(
    AstNodeCompareExpression * cmp)
{
  this->left=0;
  this->rite=0;
  this->oper=0;
  this->cmp=cmp;
}

void AstNodeBooleanOperator::dump()
{
  if(cmp) {
    cmp->dump();
  } else {
    printf("<<");
    left->dump();
    switch(oper) {
      case _and:
        printf(" .and. ");
        break;
      case _or:
        printf(" .or. ");
        break;
      case _not:
        printf(" .not. ");
        break;
      case _nop:
        printf(" .nop. ");
        break;
      default:
        assert(0);
    }
    if(rite)
      rite->dump();
    printf(">>");
  }
}

int AstNodeBooleanOperator::compile(
    ClassFile * klass, MethodInfo * method)
{
  assert(0); // TODO: throw: cannot compile this, use compileWithReloc
}

int AstNodeBooleanOperator::compileWithReloc(
    ClassFile * klass, MethodInfo * method,
    RelocTable & relocOnTrue,
    RelocTable & relocOnFalse)
{
  if(cmp) {
    u1 opcode=cmp->getOpcodeForFalse();
    cmp->compile(klass,method);
    relocOnFalse.push_back(method->getAP());
    method->assemble(opcode,0);
  } else {
    RelocTable local_relocOnTrue;
    RelocTable local_relocOnFalse;
    u4 targetTrue;
    u4 targetFalse;
    switch(oper) {
      case _and:
        left->compileWithReloc(klass,method,
            local_relocOnTrue,
            relocOnFalse);
        targetTrue=method->getAP();

        local_relocOnTrue.reloc(method,targetTrue);
        local_relocOnTrue.clear();

        rite->compileWithReloc(klass,method,
            relocOnTrue,
            relocOnFalse);
        break;
      case _or:
        left->compileWithReloc(klass,method,
            relocOnTrue,
            local_relocOnFalse);

        relocOnTrue.push_back(method->getAP());
        method->assemble(OPC_goto,0);

        targetFalse=method->getAP();
        local_relocOnFalse.reloc(method,targetFalse);

        rite->compileWithReloc(klass,method,
            relocOnTrue,
            relocOnFalse);
        break;


      case _not:
        left->compileWithReloc(klass,method,
            relocOnFalse,relocOnTrue);
        relocOnFalse.push_back(method->getAP());
        method->assemble(OPC_goto,0);

        break;

      default:
        assert(0);
    }
  }
  return 0;
}

AstNodeIfStatement::AstNodeIfStatement(AstNodeBooleanOperator * cond,
    AstNodeStatementList * true_body,
    AstNodeStatementList * false_body)
{
  this->condition=cond;
  this->true_body=true_body;
  this->false_body=false_body;
}

int AstNodeIfStatement::compile(ClassFile * klass,MethodInfo * method)
{
  u4 targetFalse;
  u4 targetTrue;

  RelocTable relocOnTrue;
  RelocTable relocOnFalse;

  condition->compileWithReloc(klass,method,relocOnTrue,relocOnFalse);

  targetTrue=method->getAP();

  true_body->compile(klass,method);

  targetFalse=method->getAP();
  if(false_body) {
    u4 relocOnEnd=method->getAP();
    method->assemble(OPC_goto,0);
    targetFalse=method->getAP();
    false_body->compile(klass,method);
    method->relocAt(relocOnEnd,method->getAP());
  }

  relocOnFalse.reloc(method,targetFalse);
  relocOnTrue.reloc(method,targetTrue);
  return 0;
}

void AstNodeIfStatement::dump()
{
  printf("IF ");
  condition->dump();
  printf("THEN ");
  true_body->dump();
  printf("END IF");
}
